#include "../include/my_head.h"
#include "../include/transfer.h"
#include <openssl/sha.h>
#include <sys/stat.h>
#include <mysql/mysql.h>

#define MMAP_THRESHOLD (100*1024*1024)
#define BUFFER_SIZE 4096

typedef struct {
    uint8_t type;       
    int  value_len;   
    char value[1000];    
} train_t;

int recvn(int sockfd, void *buf, int length);
int check_hash_in_db(MYSQL *conn, const char *hash);
void insert_file_record(MYSQL *conn, const char *username, int parent_id, const char *filename,
                        char filetype, const char *hashfilename, const char *path);

int transfile_server(const char *filename, int sockfd){
    char buf1[1024] = "/home/zhou/Desktop/zhao/three-zone-cloud-drive/server/filepath/";
    char file[1024] = {0};
    sprintf(file,"%s%s",buf1,filename);
    char sha256_hash[65];
    SHA256_CTX ctx;
    SHA256_Init(&ctx);
    int fd = open(file,O_RDWR);
    char buf[4096];
    while(1){
        bzero(buf,4096);
        ssize_t sret = read(fd,buf,4096);
        if(sret == 0){
            break;
        }
        SHA256_Update(&ctx,buf,sret);
    }
    unsigned char md[32];
    SHA256_Final(md,&ctx);;
    for(int i = 0; i < 32; i++){
        char temp[3] = {0};
        sprintf(temp,"%02x",md[i]);
        strcat(sha256_hash,temp);
    }
    printf("sha256 = %s\n",sha256_hash);
    train_t train;
    FileTransferCtx filetrans;
        
    train.value_len = strlen(filename);
    memcpy(train.value,filename,train.value_len);
    send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
    send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
    struct stat statbuf;
    fstat(fd,&statbuf);
    filetrans.total_size = statbuf.st_size;
    train.value_len = sizeof(filetrans.total_size);
    memcpy(train.value,&filetrans.total_size,train.value_len);
    send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
    send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
        
    off_t download_size = 0;
    if(recvn(sockfd,&download_size,sizeof(download_size)) == -1){
        close(fd);
        return -1;
    };
    if(filetrans.total_size > MMAP_THRESHOLD && download_size == 0){
        printf("file size > 100mb, using mmap for transfer.\n");
        char *p = (char *)mmap(NULL,filetrans.total_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        send(sockfd,p,filetrans.total_size,0);
        munmap(p,filetrans.total_size);
    }
    else{
        lseek(fd,download_size,SEEK_SET);
        while(1){
            bzero(train.value,sizeof(train.value));
            ssize_t sret = read(fd,train.value,sizeof(train.value));
            train.value_len = sret;
            send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
            send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
            if(sret == 0){
                break;
            }
        }
    }
    close(fd);
    return 0;
}


int transfile_client(const char *filename, int sockfd){
    char sha256_hash[65];
    SHA256_CTX ctx;
    SHA256_Init(&ctx);
    int fd = open(filename,O_RDWR);
    char buf[4096];
    while(1){
        bzero(buf,4096);
        ssize_t sret = read(fd,buf,4096);
        if(sret == 0){
            break;
        }
        SHA256_Update(&ctx,buf,sret);
    }
    unsigned char md[32];
    SHA256_Final(md,&ctx);;
    for(int i = 0; i < 32; i++){
        char temp[3] = {0};
        sprintf(temp,"%02x",md[i]);
        strcat(sha256_hash,temp);
    }

    printf("sha256 = %s\n",sha256_hash);
    train_t train;
    FileTransferCtx filetrans;
    bzero(&train,sizeof(train_t));
    train.value_len = strlen(sha256_hash);
    memcpy(train.value,sha256_hash,train.value_len);
    send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
    send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
    train.value_len = strlen(filename);
    memcpy(train.value,filename,train.value_len);
    send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
    send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
    int signal;
    recvn(sockfd,&signal,sizeof(signal));
    if(signal == -1){
        printf("文件%s已在服务器端存在，无需发送\n",filename);
        return 0;
    }
    struct stat statbuf;
    fstat(fd,&statbuf);
    filetrans.total_size = statbuf.st_size;
    train.value_len = sizeof(filetrans.total_size);
    memcpy(train.value,&filetrans.total_size,train.value_len);
    send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
    send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
        
    off_t download_size = 0;
    if(recvn(sockfd,&download_size,sizeof(download_size)) == -1){
        close(fd);
        return -1;
    };
    if(filetrans.total_size > MMAP_THRESHOLD && download_size == 0){
        printf("file size > 100mb, using mmap for transfer.\n");
        char *p = (char *)mmap(NULL,filetrans.total_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        send(sockfd,p,filetrans.total_size,0);
        munmap(p,filetrans.total_size);
    }
    else{
        lseek(fd,download_size,SEEK_SET);
        while(1){
            bzero(train.value,sizeof(train.value));
            ssize_t sret = read(fd,train.value,sizeof(train.value));
            train.value_len = sret;
            send(sockfd,&train.value_len,sizeof(train.value_len),MSG_NOSIGNAL);
            send(sockfd,train.value,train.value_len,MSG_NOSIGNAL);
            if(sret == 0){
                break;
            }
        }
    }
    close(fd);
    return 0;
}

int recvn(int sockfd, void *buf, int length){
    char *p = (char *)buf;
    ssize_t total = 0;
    while(total < length){
        ssize_t sret = recv(sockfd,p+total,length-total,0);
        if(sret == 0){
            return -1;
        }
        total += sret;
    }
    return 0;
}

int recvfile_client(int sockfd){
    train_t train;
    FileTransferCtx filetrans;
    recvn(sockfd,&train.value_len,sizeof(train.value_len));
    recvn(sockfd,train.value,train.value_len);
    char filename[4096] = {0};
    memcpy(filename,train.value,train.value_len);
    
    off_t download_size = 0;
    struct stat statbuf;
    int fd = open(filename,O_RDWR|O_CREAT,0666);
    if(fstat(fd,&statbuf) == 0){
        download_size = statbuf.st_size;
    }
    lseek(fd,download_size,SEEK_SET);

    recvn(sockfd,&train.value_len,sizeof(train.value_len));
    recvn(sockfd,train.value,train.value_len);
    memcpy(&filetrans.total_size, train.value, train.value_len);
    send(sockfd,&download_size,sizeof(download_size),MSG_NOSIGNAL);
    if(train.value_len > MMAP_THRESHOLD && download_size == 0){
        ftruncate(fd,filetrans.total_size);
        char *p = (char *)mmap(NULL,filetrans.total_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
        recvn(sockfd,p,filetrans.total_size);
        printf("100.00%%\n");
        munmap(p,filetrans.total_size);
    }
    else{
        off_t cursize = download_size;
        off_t slice = filetrans.total_size/10000;
        off_t lastsize = download_size;
        while(1){
            bzero(&train,sizeof(train));
            if(recvn(sockfd,&train.value_len,sizeof(train.value_len)) == -1){
                break;
            };
            cursize += train.value_len; 
            if(cursize - lastsize > slice){
                printf("%5.2lf%%\r",cursize*100.0/filetrans.total_size);
                fflush(stdout); 
                lastsize = cursize;
            }
            if(train.value_len != 1000){
                printf("train.value_len = %d\n",train.value_len);
            }
            if(train.value_len == 0){
                break;
            }
            if(recvn(sockfd,train.value,train.value_len) == -1){
                break;
            };
            write(fd,train.value,train.value_len);
        }
    }
    printf("100.00%%\n");
    close(fd);
    return 0;
}

int recvfile_server(int sockfd, MYSQL *conn, const char *username,int parent_id, const char *path){
    train_t train;
    FileTransferCtx filetrans;
    bzero(&train,sizeof(train_t));
    recvn(sockfd,&train.value_len,sizeof(train.value_len));
    recvn(sockfd,train.value,train.value_len);
    printf("hash---%s",train.value);
    char sha256_hash[65] = {0};
    sprintf(sha256_hash,"%s",train.value);
    int hash_exists = check_hash_in_db(conn, sha256_hash);
    bzero(&train,sizeof(train_t));
    recvn(sockfd,&train.value_len,sizeof(train.value_len));
    recvn(sockfd,train.value,train.value_len);
    char filename[4096] = {0};
    memcpy(filename,train.value,train.value_len);
    printf("test = %s\n",filename);
    if(hash_exists == 1){
        insert_file_record(conn,username,parent_id,filename,'f',sha256_hash,path);
        int skip_signal = -1; //通知发送端，文件已存在，无需接收
        send(sockfd,&skip_signal,sizeof(skip_signal),MSG_NOSIGNAL);
    }
    else{
        int skip_signal = 0;
        send(sockfd,&skip_signal,sizeof(skip_signal),MSG_NOSIGNAL);
        off_t download_size = 0;
        struct stat statbuf;
        int fd = open(filename,O_RDWR|O_CREAT,0666);
        if(fstat(fd,&statbuf) == 0){
            download_size = statbuf.st_size;
        }
        lseek(fd,download_size,SEEK_SET);

        recvn(sockfd,&train.value_len,sizeof(train.value_len));
        recvn(sockfd,train.value,train.value_len);
        memcpy(&filetrans.total_size, train.value, train.value_len);
        send(sockfd,&download_size,sizeof(download_size),MSG_NOSIGNAL);
        if(train.value_len > MMAP_THRESHOLD && download_size == 0){
            ftruncate(fd,filetrans.total_size);
            char *p = (char *)mmap(NULL,filetrans.total_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
            recvn(sockfd,p,filetrans.total_size);
            munmap(p,filetrans.total_size);
        }
        else{
            off_t cursize = download_size;
            off_t slice = filetrans.total_size/10000;
            off_t lastsize = download_size;
            while(1){
                bzero(&train,sizeof(train));
                if(recvn(sockfd,&train.value_len,sizeof(train.value_len)) == -1){
                    break;
                };
                cursize += train.value_len; 
                if(cursize - lastsize > slice){
                    printf("%5.2lf%%\r",cursize*100.0/filetrans.total_size);
                    fflush(stdout); 
                    lastsize = cursize;
                }
                if(train.value_len != 1000){
                    printf("train.value_len = %d\n",train.value_len);
                }
                if(train.value_len == 0){
                    break;
                }
                if(recvn(sockfd,train.value,train.value_len) == -1){
                    break;
                };
                write(fd,train.value,train.value_len);
            }
            printf("100.00%%\n");
        }
        close(fd);
        insert_file_record(conn,username,parent_id,filename,'f',sha256_hash,path);
    }
    return 0;
}

int check_hash_in_db(MYSQL *conn, const char *hash){
    MYSQL_RES *res;
    MYSQL_ROW row;
    char query[512];
    sprintf(query,"select * from file where hashfilename = '%s'",hash);
    if(mysql_query(conn,query)){
        fprintf(stderr, "%s\n",mysql_error(conn));
        return -1;
    }
    res = mysql_store_result(conn);
    int num_rows = mysql_num_rows(res);
    mysql_free_result(res);
    return (num_rows > 0) ? 1 : 0; // 存在返回1，不存在返回0
}

void insert_file_record(MYSQL *conn, const char *username, int parent_id, const char *filename,
                        char filetype, const char *hashfilename, const char *path){
    char query[1024];
    sprintf(query,"insert into file(username, parent_id, filename, filetype, hashfilename, path, tomb) value('%s','%d','%s','%c','%s','%s',0)"
            ,username,parent_id,filename,filetype,hashfilename,path);
    if(mysql_query(conn,query)){
        fprintf(stderr,"插入数据库记录失败：%s\n",mysql_error(conn));
    }
    else{
        printf("插入数据库记录成功：%s\n",filename);
    }
}

